Add autoscroll when dragging past boundary of range
authorWilliam Jon McCann <william.jon.mccann@gmail.com>
Mon, 29 Jul 2013 16:06:20 +0000 (12:06 -0400)
committerMatthias Clasen <mclasen@redhat.com>
Mon, 5 Aug 2013 06:47:27 +0000 (08:47 +0200)
A problem with the zoom scroll mode is that you have to restart
if you hit the bottom of the screen before you hit the bottom
of your document.

This commit adds an autoscroll feature to the zoom scroll: if
you move outside the window while in zoom scroll mode, we keep
scrolling in the direction you were going until you let go
of the mouse button.

https://bugzilla.gnome.org/show_bug.cgi?id=704703

gtk/gtkrange.c

index f99c309cb2a5dadf883017988b429871e9c8b23e..aff4ff3eaab28b2b3b776981648123de8a899fd6 100644 (file)
@@ -64,6 +64,7 @@
 #define TIMEOUT_INITIAL     500
 #define TIMEOUT_REPEAT      50
 #define ZOOM_HOLD_TIME      500
+#define AUTOSCROLL_FACTOR   20
 
 typedef struct _GtkRangeStepTimer GtkRangeStepTimer;
 
@@ -116,7 +117,7 @@ struct _GtkRangePrivate
   gint  n_marks;
   gint  round_digits;                /* Round off value to this many digits, -1 for no rounding */
   gint  slide_initial_slider_position;
-  gint  slide_initial_coordinate;
+  gint  slide_initial_coordinate_delta;
   gint  slider_start;                /* Slider range along the long dimension, in widget->window coords */
   gint  slider_end;
 
@@ -146,6 +147,8 @@ struct _GtkRangePrivate
   guint zoom                   : 1;
   guint zoom_set               : 1;
   GtkPressAndHold *press_and_hold;
+  GtkScrollType autoscroll_mode;
+  guint autoscroll_id;
 
   /* Fill level */
   guint show_fill_level        : 1;
@@ -2536,8 +2539,8 @@ gtk_range_key_press (GtkWidget   *widget,
       stop_scrolling (range);
 
       update_slider_position (range,
-                             priv->slide_initial_coordinate,
-                             priv->slide_initial_coordinate);
+                             priv->slide_initial_coordinate_delta + priv->slide_initial_slider_position,
+                             priv->slide_initial_coordinate_delta + priv->slide_initial_slider_position);
 
       return TRUE;
     }
@@ -2732,12 +2735,12 @@ gtk_range_button_press (GtkWidget      *widget,
       if (priv->orientation == GTK_ORIENTATION_VERTICAL)
         {
           priv->slide_initial_slider_position = priv->slider.y;
-          priv->slide_initial_coordinate = event->y;
+          priv->slide_initial_coordinate_delta = event->y - priv->slider.y;
         }
       else
         {
           priv->slide_initial_slider_position = priv->slider.x;
-          priv->slide_initial_coordinate = event->x;
+          priv->slide_initial_coordinate_delta = event->x - priv->slider.x;
         }
 
       range_grab_add (range, device, MOUSE_SLIDER, event->button);
@@ -2770,11 +2773,6 @@ update_slider_position (GtkRange *range,
   gdouble zoom;
   gint i;
 
-  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
-    delta = mouse_y - priv->slide_initial_coordinate;
-  else
-    delta = mouse_x - priv->slide_initial_coordinate;
-
   if (priv->zoom)
     {
       zoom = MIN(1.0, (priv->orientation == GTK_ORIENTATION_VERTICAL ?
@@ -2789,6 +2787,20 @@ update_slider_position (GtkRange *range,
   else
     zoom = 1.0;
 
+  /* recalculate the initial position from the current position */
+  if (priv->slide_initial_slider_position == -1)
+    {
+      if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+        priv->slide_initial_slider_position = (zoom * (mouse_y - priv->slide_initial_coordinate_delta) - priv->slider.y) / (zoom - 1.0);
+      else
+        priv->slide_initial_slider_position = (zoom * (mouse_x - priv->slide_initial_coordinate_delta) - priv->slider.x) / (zoom - 1.0);
+    }
+
+  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+    delta = mouse_y - (priv->slide_initial_coordinate_delta + priv->slide_initial_slider_position);
+  else
+    delta = mouse_x - (priv->slide_initial_coordinate_delta + priv->slide_initial_slider_position);
+
   c = priv->slide_initial_slider_position + zoom * delta;
 
   new_value = coord_to_value (range, c);
@@ -2813,11 +2825,67 @@ update_slider_position (GtkRange *range,
                  &handled);
 }
 
+static void
+remove_autoscroll (GtkRange *range)
+{
+  if (range->priv->autoscroll_id)
+    {
+      gtk_widget_remove_tick_callback (GTK_WIDGET (range),
+                                       range->priv->autoscroll_id);
+      range->priv->autoscroll_id = 0;
+    }
+
+  /* unset initial position so it can be calculated */
+  range->priv->slide_initial_slider_position = -1;
+
+  range->priv->autoscroll_mode = GTK_SCROLL_NONE;
+}
+
+static gboolean
+autoscroll_cb (GtkWidget     *widget,
+               GdkFrameClock *frame_clock,
+               gpointer       data)
+{
+  GtkRange *range;
+  gdouble increment;
+  gdouble value;
+  gboolean handled;
+
+  range = GTK_RANGE (data);
+
+  increment = gtk_adjustment_get_step_increment (range->priv->adjustment) / AUTOSCROLL_FACTOR;
+  if (range->priv->autoscroll_mode == GTK_SCROLL_STEP_BACKWARD)
+    increment *= -1;
+
+  value = gtk_adjustment_get_value (range->priv->adjustment);
+  value += increment;
+  g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, value,
+                 &handled);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+add_autoscroll (GtkRange *range)
+{
+  GtkRangePrivate *priv = range->priv;
+
+  if (priv->autoscroll_id != 0
+      || priv->autoscroll_mode == GTK_SCROLL_NONE)
+    return;
+
+  priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (range),
+                                                      (GtkTickCallback)autoscroll_cb,
+                                                      range,
+                                                      NULL);
+}
+
 static void
 stop_scrolling (GtkRange *range)
 {
   range_grab_remove (range);
   gtk_range_remove_step_timer (range);
+  remove_autoscroll (range);
 }
 
 static gboolean
@@ -2873,13 +2941,12 @@ gtk_range_button_release (GtkWidget      *widget,
     {
       if (priv->grab_location == MOUSE_SLIDER)
         {
-          update_slider_position (range, priv->mouse_x, priv->mouse_y);
           if (priv->press_and_hold)
             gtk_press_and_hold_process_event (priv->press_and_hold, (GdkEvent *)event);
         }
 
       stop_scrolling (range);
-      
+
       return TRUE;
     }
 
@@ -2962,6 +3029,43 @@ gtk_range_scroll_event (GtkWidget      *widget,
   return TRUE;
 }
 
+static void
+update_autoscroll_mode (GtkRange *range)
+{
+  GtkScrollType mode = GTK_SCROLL_NONE;
+
+  if (range->priv->zoom)
+    {
+      GtkAllocation allocation;
+      gint size, pos;
+
+      gtk_widget_get_allocation (GTK_WIDGET (range), &allocation);
+
+      if (range->priv->orientation == GTK_ORIENTATION_VERTICAL)
+        {
+          size = allocation.height;
+          pos = range->priv->mouse_y;
+        }
+      else
+        {
+          size = allocation.width;
+          pos = range->priv->mouse_x;
+        }
+
+      if (pos < 0)
+        mode = GTK_SCROLL_STEP_BACKWARD;
+      else if (pos > size)
+        mode = GTK_SCROLL_STEP_FORWARD;
+    }
+
+  if (mode != range->priv->autoscroll_mode)
+    {
+      remove_autoscroll (range);
+      range->priv->autoscroll_mode = mode;
+      add_autoscroll (range);
+    }
+}
+
 static gboolean
 gtk_range_motion_notify (GtkWidget      *widget,
                         GdkEventMotion *event)
@@ -2982,7 +3086,9 @@ gtk_range_motion_notify (GtkWidget      *widget,
       if (!priv->zoom_set && priv->press_and_hold != NULL)
         gtk_press_and_hold_process_event (priv->press_and_hold, (GdkEvent *)event);
 
-      update_slider_position (range, event->x, event->y);
+      update_autoscroll_mode (range);
+      if (priv->autoscroll_mode == GTK_SCROLL_NONE)
+        update_slider_position (range, event->x, event->y);
     }
 
   /* We handled the event if the mouse was in the range_rect */